package com.fuluwa.domain.bank.model;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.OptimisticLockType;
import org.hibernate.annotations.OptimisticLocking;

import com.fuluwa.domain.bank.service.AccountException;
import com.fuluwa.domain.security.model.User;
import com.fuluwa.domain.shared.AbstractDomainObject;
import com.fuluwa.domain.shared.AbstractIdDomainObject;
import com.fuluwa.infra.exception.PersistenceException;
import com.fuluwa.infra.utils.MoneyFormat;

@Entity
@Table(name = "b_acct")
@OptimisticLocking(type = OptimisticLockType.VERSION)
public class Account extends AbstractIdDomainObject {
	public static enum AccountType {
		GOLD(GeneralLedger.GoldLibility.getGlCode()), CASH(GeneralLedger.CashLibility.getGlCode());
		Integer glCode;

		private AccountType(Integer glCode) {
			this.glCode = glCode;
		}

		public Integer getGlCode() {
			return glCode;
		}
	}

	public static enum AccountStatus {
		CLOSED, BLOCKED, OPENED, DORMANT
	}

	/*@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_acct")
	@SequenceGenerator(name = "seq_acct", allocationSize = 1, initialValue = 1, sequenceName = "seq_acct")
	@Id
	private Long id;*/

	@Column(unique=true)
	private String accountNo;

	@Column(name = "bal_avail", columnDefinition = "decimal(13,2)") 
	private BigDecimal availableBalance = new BigDecimal(0.00);
	
	@Column(name = "bal_hold", columnDefinition = "decimal(13,2)")
	private BigDecimal holdBalance = new BigDecimal(0.00);

	@Enumerated(EnumType.STRING)
	private AccountType accountType = AccountType.GOLD;

	@Enumerated(EnumType.STRING)
	private AccountStatus status = AccountStatus.OPENED;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(name = "dat_opening")
	private Date openingDate = new Date();

//	@ManyToOne
//	@JoinColumn(name = "cod_bank")
	@Embedded
	private Bank bank = Bank.getDefaultBank();

	@ManyToOne
	@JoinColumn(name = "cod_user_id")
	private User user;

	@Column(name = "cod_VERSION")
	@Version
	private Integer version = 0;

	/*@Override
	protected Serializable getDomainId() {
		return id;
	}*/

	private Account() {
	}

	public Account(String accountNo) {
		this.accountNo = accountNo;
	}

	public Account(Long id) {
		this.id = id;
	}

	@Override
	public void save() throws PersistenceException {
		super.save();
		accountNo = getAccountType().getGlCode() + StringUtils.leftPad(id + "", 15, "0");
		update();
	}

	public boolean isCashAccount() {
		return AccountType.CASH.equals(accountType);
	}

	public AccountType getAccountType() {
		return accountType;
	}

	public Account(User user, AccountType accountType) {
		this.accountType = accountType;
		this.user = user;
	}

	public Long getId() {
		return id;
	}

	public String getAccountNo() {
		return accountNo;
	}

	public BigDecimal getAvailableBalance() {
		return availableBalance;
	}

	public AccountStatus getStatus() {
		return status;
	}

	public Date getOpeningDate() {
		return openingDate;
	}

	public Bank getBank() {
		return bank;
	}

	public User getUser() {
		return user;
	}

	public Integer getVersion() {
		return version;
	}

	/**
	 * Asset add,and libility minus,like withdrawl
	 * @throws PersistenceException 
	 */
	public void doDebit(BigDecimal amount) throws PersistenceException {
		validateStatus();
		substractBalance(amount);
		this.update();
	}

	private void substractBalance(BigDecimal amount) {
		if(availableBalance.compareTo(amount)<0) {
			throw new AccountException("Account Balance is not enough:current balance is:"+MoneyFormat.format(availableBalance)+",require:"+MoneyFormat.format(amount));
		}
		availableBalance= availableBalance.subtract(amount);
	}
	/**deposit
	 * @param amount
	 * @throws PersistenceException
	 */
	public void doCredit(BigDecimal amount) throws PersistenceException {
		validateStatus();
		availableBalance= availableBalance.add(amount);
		this.update();
	}
	
	public void validateStatus() {
		switch (status) {
		case OPENED:
			break;
		default:
			throw new AccountException("Account status is invalidate,current status is:" + status);
		}
	}
	
	public void hold(BigDecimal amount) throws PersistenceException {
		validateStatus();
		substractBalance(amount);
		holdBalance=holdBalance.add(amount);
		this.update();
	}
	public void unhold(BigDecimal amount) throws PersistenceException {
		validateStatus();
		if(holdBalance.compareTo(amount)<0) {
			throw new AccountException("Account Hold Balance is not enough:current hold balance is:"+MoneyFormat.format(holdBalance)+",require:"+MoneyFormat.format(amount));
		}
		availableBalance= availableBalance.add(amount);
		holdBalance=holdBalance.subtract(amount);
		this.update();
	}
	
	public void close() throws PersistenceException {
		this.status=AccountStatus.CLOSED;
		this.update();
	}

	public BigDecimal getHoldBalance() {
		return holdBalance;
	}
	public BigDecimal getTotalBalance() {
		return holdBalance.add(availableBalance);
	}
}
